/**
 * org.code.google.generator.AbstractGenerator.java
 * @since Jan 25 2009
 */
package org.code.google.generator;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * Abstract generator implementation
 * 
 * @author Rafael Nami
 */
public abstract class AbstractGenerator implements Generator {

	private static final long serialVersionUID = 1L;
	// templateFolder attribute
	private String templateFolder;
	// templateName attribute
	private String templateName;
	// outputFolder attribute
	private String outputFolder;
	// outputName attribute
	private String outputName;
	// dataModel attribute
	private Object dataModel;
	// configuration attribute
	private Configuration configuration;
	// template attribute
	private Template template;
	// dirPath attribute
	private File dirPath;
	// filePath attribute
	private File filePath;
	// writer attribute
	private Writer writer;

	/**
	 * Standard Constructor
	 */
	public AbstractGenerator() {
	}

	/**
	 * Constructor that allows the writer to be injected Can be used for testing
	 * purposes
	 * 
	 * @param writer
	 */
	public AbstractGenerator(Writer writer) {
		this.writer = writer;
	}

	/**
	 * @return the writer
	 */
	public Writer getWriter() {
		return writer;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void generate() throws GeneratorException {
		try {
			configureGeneration();
			configureTemplate();
			configureDirectories();
			configureFileOutput();
			generateFilePath();
			applyTemplate();
		} catch (GeneratorException e) {
			throw e;
		} catch (Exception e) {
			throw new GeneratorException("Error on applying the template", e);
		}

	}

	/**
	 * Template method to initialize the template engine.
	 * 
	 * @throws GeneratorException
	 */
	protected void configureGeneration() throws GeneratorException {
		configuration = new Configuration();
		try {
			configuration.setDirectoryForTemplateLoading(new File(
					getTemplateFolder()));
			configuration.setObjectWrapper(BeansWrapper.getDefaultInstance());
		} catch (IOException e) {
			throw new GeneratorException(
					"Error on the template loading! The provided directory doesn't exist or is not readable!");
		}
	}

	/**
	 * Template method to configure the template.
	 * 
	 * @throws GeneratorException
	 */
	protected void configureTemplate() throws GeneratorException {
		try {
			template = configuration.getTemplate(getTemplateName());
		} catch (IOException e) {
			throw new GeneratorException(
					"Error on the template loading! The provided directory doesn't exist or is not readable!");
		}
	}

	/**
	 * Template method to configure the generation of the directory structure of
	 * the project
	 */
	protected void configureDirectories() {
		dirPath = new File(getOutputFolder());
	}

	/**
	 * Template method to configure the generation of the file
	 */
	protected void configureFileOutput() {
		filePath = new File(new StringBuilder().append(getOutputFolder())
				.append("/").append(getOutputName()).toString());
	}

	/**
	 * Template method to generate the file path
	 * 
	 * @throws IOException
	 */
	protected void generateFilePath() throws IOException {
		if (!(filePath.exists())) {
			dirPath.mkdirs();
			filePath.createNewFile();
		}
	}

	/**
	 * Template model to apply the configured data model to the configure
	 * freemarker template.
	 * 
	 * @throws IOException
	 * @throws TemplateException
	 */
	protected void applyTemplate() throws IOException, TemplateException {
		if (writer == null) {
			writer = new FileWriter(filePath);
		}
		template.process(getDataModel(), writer);
		writer.flush();
		writer.close();
	}

	/**
	 * @return the templateFolder
	 */
	public String getTemplateFolder() {
		return templateFolder;
	}

	/**
	 * @return the templateName
	 */
	public String getTemplateName() {
		return templateName;
	}

	/**
	 * @return the outputFolder
	 */
	public String getOutputFolder() {
		return outputFolder;
	}

	/**
	 * @return the outputName
	 */
	public String getOutputName() {
		return outputName;
	}

	/**
	 * @return the dataModel
	 */
	public Object getDataModel() {
		return dataModel;
	}

	/**
	 * @param templateFolder
	 *            the templateFolder to set
	 */
	public void setTemplateFolder(String templateFolder) {
		this.templateFolder = templateFolder;
	}

	/**
	 * @param templateName
	 *            the templateName to set
	 */
	public void setTemplateName(String templateName) {
		this.templateName = templateName;
	}

	/**
	 * @param outputFolder
	 *            the outputFolder to set
	 */
	public void setOutputFolder(String outputFolder) {
		this.outputFolder = outputFolder;
	}

	/**
	 * @param outputName
	 *            the outputName to set
	 */
	public void setOutputName(String outputName) {
		this.outputName = outputName;
	}

	/**
	 * @param dataModel
	 *            the dataModel to set
	 */
	public void setDataModel(Object dataModel) {
		this.dataModel = dataModel;
	}
}
